class Module {
  constructor (moduleName,source) {
    this.exports = {}
    this.moduleName = moduleName
    this.$source = source
    this.cache = new Map()
  }

  $runInThisContext=(code, whiteList=['console'])=>{
    const func = new Function('sendbox',`with(sendbox){${code}}`)
    // console.log('code',code)
    return function (sendbox) {
      if (!sendbox || typeof sendbox !== 'object') {
        throw Error('sendbox must be is object')
      }
      const proxiedObjecet = new Proxy(sendbox,{
        has (target, key) {
          if (!whiteList.includes(key)){
            return ({})
          }
        },
        get(target, key, receiver){
          if (key === Symbol.unscopables){
            return void 0;
          }
          return  Reflect.get(target, key, receiver)
        }
      })
      return func(proxiedObjecet)
    }
  }

  require (moduleName, source){
    if (this.cache.has(moduleName)) {
      return this.cache.get(moduleName).exports
    }
    // console.log(this)
    const module = new Module(moduleName, source)
    const exports = this.compile(module,source)
    this.cache.set(moduleName, module)
    return exports
  }
  $wrap=(code)=>{
    const wrapper = [
      'return (function(module,exports,require){',
      '\n})'
    ]
    return `${wrapper[0]}${code}${wrapper[1]}`
  }
  compile=(module, source)=>{
    const iifeString = this.$wrap(source)
    const compiler = this.$runInThisContext(iifeString)({})
    compiler.call(module,module,module.exports,this.require)
    // console.log(module)
    return module.exports
  }
}


/**
 * demo验证
 */
 const m = new Module()

 //a.js
//  const sourceCodeFromModule = `
//    const b = require('./b.js','exports.action = function(){console.log("this is b module")}')
//    console.log("this is a module")
//    b.action()
//  `
//  console.log(m)
//  m.require.call(m,'a.js',sourceCodeFromModule)

class Lu {
  constructor(name){
    this.name = name
    this.age = 10
  }
  sayName=()=>{
    console.log(this.name)
    this.sayAge()
  }
  sayAge(){
    console.log('age==='+this.age)
  }
}

let lu = new Lu('luvy')
console.log(lu)
lu.sayName()

lu instanceof Object